/*
 * Copyright (C) 2018 - 2019, Jue Huang <rabbit@pear.hk>
 *
 * CheckTx Flow
 * 1. Validate inputs and outputs, basic ( address, not zero, seqence check)
 * 2. Get inputs account
 * 3. Get or Create outputs account
 * 4. Validate inputs and outputs, advanced (signature check)
 * 5. Fee validation for SendTx
 * 6. If DeliverTx , Do 
 */

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <memory.h>

#include "log/pear_log.h"
#include "pear_tx_serial.h"
#include "utils/pear_dump.h"
#include "pear_tx_default.h"
#include "crypto/pear_key.h"
#include "account/pear_account.h"

static int pr_tx_check(pr_acc_ctx_t *,   void *, int);
static int pr_tx_deliver(pr_acc_ctx_t *, void *, int);

static int pr_tx_input_valid(pr_tx_in_t *);
static int pr_tx_output_valid(pr_tx_out_t *);
static int pr_tx_valid_check_in(pr_tx_t *, pr_acc_ctx_t *, bool);


pr_tx_default_t *pr_new_tx_default()
{
    pr_tx_default_t *txd = NULL;

    txd = (pr_tx_default_t *) malloc(sizeof(*txd));
    if (txd == NULL)
    {
        PEAR_LOG("malloc pr_tx_default_t error\n");
        return NULL;
    }

    txd->tx_ctx.check_tx   = &pr_tx_check;
    txd->tx_ctx.deliver_tx = &pr_tx_deliver;

    return txd;
}

static int pr_tx_deliver(pr_acc_ctx_t *acc_ctx, void *txstr, int tx_len)
{
    int ret = 0;
    pr_tx_t *tx = NULL;

    /*
     * For Debug
     */
    PEAR_LOG("txstr pr_tx_encode: %s\n", pr_tx_encode(txstr));

    /*
     * Decode Tx
     */
    tx = pr_tx_decode(txstr, tx_len);
    if (tx == NULL)
    {
        PEAR_LOG("Tx Decode Error\n");
        return -1;
    }

    PEAR_LOG("tx pr_tx_encode: %s\n", pr_tx_encode(tx));
    ret = pr_tx_valid_check_in(tx, acc_ctx, true);

    free(tx);

    return ret;
}

static int pr_tx_input_valid(pr_tx_in_t *input)
{
    if (input->amount == 0)
    {
        return -1;
    }

    return 0;
}

static int pr_update_balance(pr_tx_t *tx, pr_acc_ctx_t *acc_ctx)
{
    return 1;
}

static int pr_tx_output_valid(pr_tx_out_t *output)
{
    if (output->amount == 0)
    {
        return -1;
    }

    return 0;
}

static int pr_tx_check(pr_acc_ctx_t *acc_ctx, void *txstr, int tx_len)
{
    int ret = 0;
    pr_tx_t *tx = NULL;

    /*
     * For Debug
     */
    char buf[1024] = {0x00};
    PEAR_LOG("txstr pr_tx_encode: %s\n", pr_tx_encode(txstr));

    /*
     * Decode Tx
     */
    tx = pr_tx_decode(txstr, tx_len);
    if (tx == NULL)
    {
        PEAR_LOG("Tx Decode Error\n");
        return -1;
    }

    PEAR_LOG("tx pr_tx_encode: %s\n", pr_tx_encode(tx));
    ret = pr_tx_valid_check_in(tx, acc_ctx, false);

    free(tx);

    return ret;
}

static int pr_tx_valid_check_in(pr_tx_t *tx, pr_acc_ctx_t *acc_ctx, bool deliver)
{
    char addr_buf[32];

    pr_account_t *in_acc  = NULL;
    pr_account_t *out_acc = NULL;

    /*
     * Tx Input Valid Check
     */
    if (pr_tx_input_valid(&tx->input) < 0)
    {
        PEAR_LOG("Input Invalid\n");
        return -1;
    }

    /*
     * Tx Output Valid Check
     */
    if (pr_tx_output_valid(&(tx->output)) < 0)
    {
        PEAR_LOG("Output Invalid\n");
        return -1;
    }

    /*
     * Input == Output
     */
    if (tx->input.amount != tx->output.amount)
    {
        PEAR_LOG("Input Output Not equal\n");
        return -1;
    }

    /*
     * Get Input Account
     */
    PEAR_LOG("input addr: %s\n", pr_hex_encode(addr_buf, tx->input.from, sizeof(tx->input.from)));

    if ((in_acc = pr_get_account(acc_ctx, pr_hex_encode(addr_buf, tx->input.from, sizeof(tx->input.from))) ) == NULL)
    {
        PEAR_LOG("Account not exist\n");
        return -1;
    }

    /*
     * Fee And Balance Check
     */
    if (in_acc->balance - tx->fee * tx->gas < tx->input.amount)
    {
        PEAR_LOG("Balance not enough\n");
        free(in_acc);
        return -1;
    }

/*
    if (tx->input.sequence <= in_acc->sequence)
    {
        PEAR_LOG("Transaction Sequence Error\n");
        free(in_acc);
        return -1;
    }
*/

    /*
     * Signature Check
     */
    if (pr_verify((char *)tx, sizeof(*tx), &tx->input.sign, &tx->input.key) < 0)
    {
        PEAR_LOG("Signature Invalid\n");
        free(in_acc);
        return -1;
    }

    if (deliver == false)
    {
        return 0;
    }
    /*
     * Get or Make Output Account
     */
    if (( out_acc = pr_get_account(acc_ctx, pr_hex_encode(addr_buf, tx->output.to, sizeof(tx->output.to))) ) == NULL)
    {
        out_acc = (pr_account_t *) malloc(sizeof(*out_acc));
        if (out_acc == NULL)
        {
            PEAR_LOG("Malloc out_acc error\n");
            free(in_acc);
            return -1;
        }

        memset(out_acc, 0x00, sizeof(*out_acc));

    }

    /*
     * Update Account Balance
     */
    in_acc->sequence = tx->input.sequence;
    in_acc->balance  = in_acc->balance - tx->input.amount - tx->fee * tx->gas;
    out_acc->balance += tx->input.amount;

    if ((out_acc = pr_set_account(acc_ctx, pr_hex_encode(addr_buf, tx->output.to, sizeof(tx->output.to)), out_acc)) == NULL)
    {
        free(in_acc);
        free(out_acc);
        return -1;
    }

    if ((in_acc = pr_set_account(acc_ctx, pr_hex_encode(addr_buf, tx->input.from, sizeof(tx->input.from)), in_acc)) == NULL)
    {
        free(in_acc);
        free(out_acc);
        return -1;
    }

    if (pr_update_balance(tx, acc_ctx) < 0)
    {
        free(in_acc);
        free(out_acc);
        return -1;
    }

    return 0;
}


